/*
 * Copyright 2024 Hangzhou Yingyi Technology Co., Ltd
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <uk/plat/common/bootinfo.h>
#include <arm/cpu.h>
#include <raspi/console.h>
#include <uk/intctlr.h>
#include <raspi/gpio.h>
#include <raspi/mbox.h>

smccc_conduit_fn_t smccc_psci_call;

static char *cmdline;
static __sz cmdline_len;

static inline int cmdline_init(struct ukplat_bootinfo *bi)
{
	char *cmdl;

	if (bi->cmdline_len) {
		cmdl = (char *)bi->cmdline;
		cmdline_len = bi->cmdline_len;
	} else {
		cmdl = CONFIG_UK_NAME;
		cmdline_len = sizeof(CONFIG_UK_NAME) - 1;
	}

	/* TODO: Only extract what we need before passing to application. */

	/* This is not the original command-line, but one that will be thrashed
	 * by `ukplat_entry_argp` to obtain argc/argv. So mark it as a kernel
	 * resource instead.
	 */
	cmdline = ukplat_memregion_alloc(cmdline_len + 1, UKPLAT_MEMRT_KERNEL,
					 UKPLAT_MEMRF_READ |
					 UKPLAT_MEMRF_WRITE |
					 UKPLAT_MEMRF_MAP);
	if (unlikely(!cmdline))
		return -ENOMEM;

	memcpy(cmdline, cmdl, cmdline_len);
	cmdline[cmdline_len] = 0;

	return 0;
}

static void __noreturn _ukplat_entry2(void)
{
	ukplat_entry_argp(NULL, cmdline, cmdline_len);

	ukplat_lcpu_halt();
}


void _libraspiplat_entry(void *fdtp)
{
	int rc;
	void *bstack;
	struct ukplat_bootinfo *bi;

	_libraspiplat_init_mbox(fdtp);
	_libraspiplat_init_gpio(fdtp);
	_libraspiplat_init_console(fdtp);

	ukplat_bootinfo_fdt_setup(fdtp);
	bi = ukplat_bootinfo_get();
	if (unlikely(!bi))
		UK_CRASH("Invalid bootinfo");

	uk_pr_debug("Device tree location @%p\n", bi);

	rc = cmdline_init(bi);
	if (unlikely(rc < 0))
		UK_CRASH("Failed to initialize command-line\n");

	/* Allocate boot stack */
	bstack = ukplat_memregion_alloc(__STACK_SIZE, UKPLAT_MEMRT_STACK,
					UKPLAT_MEMRF_READ |
					UKPLAT_MEMRF_WRITE |
					UKPLAT_MEMRF_MAP);
	if (unlikely(!bstack))
		UK_CRASH("Boot stack alloc failed\n");
	bstack = (void *)((__uptr)bstack + __STACK_SIZE);

	/* Initialize paging */
	rc = ukplat_mem_init();
	if (unlikely(rc))
		UK_CRASH("Could not initialize paging (%d)\n", rc);

#if defined(CONFIG_ENFORCE_W_XOR_X) && defined(CONFIG_PAGING)
	enforce_w_xor_x();
#endif /* CONFIG_ENFORCE_W_XOR_X && CONFIG_PAGING */

	/* Initialize interrupt controller */
	rc = uk_intctlr_probe();
	if (unlikely(rc))
		UK_CRASH("Could not initialize the IRQ controller: %d\n", rc);

	/* Initialize logical boot CPU */
	rc = lcpu_init(lcpu_get_bsp());
	if (unlikely(rc))
		UK_CRASH("Failed to initialize bootstrapping CPU: %d\n", rc);

	/*
	 * Switch away from the bootstrap stack as early as possible.
	 */
	uk_pr_info("Switch from bootstrap stack to stack @%p\n", bstack);

	/* Print boot information */
	uk_pr_info("Print bootinfo before jump to entry:\n");
	ukplat_bootinfo_print();

	/*
	 * Enter Unikraft with new allocated stack.
	 */
	lcpu_arch_jump_to(bstack, _ukplat_entry2);
}
